﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Reflection;
using GalaSoft.MvvmLight.Messaging;
using System.Threading;
using GalaSoft.MvvmLight.Messaging;


namespace XReactor.XSockets
{
   public delegate void OnDisconnect(XReactorSockets nClient);
   public delegate void OnReceivedLoginResponse(LoginRequestResponse nLoginRequestResponse);
   public delegate void OnReceivedGamePermResponse(GamePermRequestResponse nGamePermRequestResponse);
  public delegate void OnReceivedUserInfoResponse(UserInfoRequestResponse nInfoRequestResponse);

     public class XReactorSockets : IDisposable
    {

        #region Variables

        Socket m_ServerSocket;

        string m_Name;

        bool m_Connected;

        int attempt = 0;

        public OnDisconnect m_OnDisonnection;

        public OnReceivedUserInfoResponse OnUserInfoRequestResponse;

        public OnReceivedLoginResponse OnLoginRequestResponse;

        public OnReceivedGamePermResponse OnGamePermRequestResponse;

        IPEndPoint m_ServerEp;

       
        #endregion

        #region Properties


        public IPEndPoint ServerEp
        {
            get { return m_ServerEp; }
            set { m_ServerEp = value; }
        }    

        public Socket ClientSocket
        {
            get { return m_ServerSocket; }
            set { m_ServerSocket = value; }
        }
        
        public string Name
        {
            get { return m_Name; }
            set { m_Name = value; }
        }        
        
        public bool Connected
        {
            get { return m_Connected;  }
            set { m_Connected = value; }
        }
        
        #endregion 

        #region Socket Functions
 

        public void TryConnect()
        {
           
            try
            {
                XPacket xp = new XPacket();
                m_ServerSocket.BeginConnect(ServerEp, new AsyncCallback(OnConnect), null);
            }
            catch (SocketException se)
            {
                Console.WriteLine("Attemting To Connect "+attempt);
                attempt++;
                if (attempt == 11)
                {
                    return;
                }
                Messenger.Default.Send<string>("TRY_TO_CONNECT", "loginform");
                Thread.Sleep(2000);
                TryConnect();
            }       
        }

        private void OnConnect(IAsyncResult ar)
        {
            m_ServerSocket.EndConnect(ar);
            Console.WriteLine("Client Connected To The Server...");
            // If There are a connection.Start Listen
            XPacket xp = new XPacket();
            m_ServerSocket.BeginReceive(xp.Buffer, 0, xp.Buffer.Length, SocketFlags.None, new AsyncCallback(OnReceive), xp);
        }

        private void OnReceive(IAsyncResult ar)
        {
            try
            {
                XPacket xp = (XPacket)ar.AsyncState;
                int ret = m_ServerSocket.EndReceive(ar);
                ExtractBuffer(xp.Buffer, ret);
                xp = new XPacket();
                m_ServerSocket.BeginReceive(xp.Buffer, 0, xp.Buffer.Length, SocketFlags.None, new AsyncCallback(OnReceive), xp);
            }
            catch (SocketException s)
            {
                if (s.SocketErrorCode == SocketError.ConnectionReset)
                {
                    Console.WriteLine("Connection Reset By Server");
                }
                Connected = false;
                if (m_OnDisonnection != null)
                {
                    m_OnDisonnection(this);
                }
            }

        }

        private void OnSend(IAsyncResult ar)
        {
            int ret = ClientSocket.EndSend(ar);
            if (ret <= 0)
            {
                this.Connected = false;
                if (m_OnDisonnection != null)
                {
                    m_OnDisonnection(this);
                    Dispose();
                }
            }
        } 
        #endregion

        #region Receive Functions

        #endregion

        #region Send Functions
        private void SendLoginRequest(string nUsername,string nPassword)
        {
            LoginRequest.Builder Lr = new LoginRequest.Builder();
            Lr.SetName(nUsername);
            Lr.SetPassword(nPassword);
            Lr.SetVersion("1");           
            CreateCommand(XCommunicateEnum.LoginRequest, Lr.Build().ToByteArray());            
        }
        #endregion

        #region Utils
        /// <summary>
        /// Ham Komut Paketi Oluşturur
        /// </summary>
        /// <param name="Command">Gönderilecek Komutun Türü</param>
        /// <param name="data">Komutun Datası</param>
        /// <returns>Saf Gonderilecek Veri Döndürür</returns>
        private byte[] CreateCommand(XCommunicateEnum Command, byte[] data)
        {
            byte[] temp = new byte[2 + data.Length];
            temp[0] = (byte)XCommunicateEnum.Header;
            temp[1] = (byte)Command;
            Array.Copy(data, 0, temp, 2, data.Length);
            return temp;
        }
        
        /// <summary>
        /// Veriyi Bastaki 2 bytedan Ayrıştırır
        /// </summary>
        /// <param name="data">Ham Veri</param>
        /// <returns>Saf Komut Paketi Döndürür</returns>
        private byte[] GetCommand(byte[] data)
        {
            byte[] command = new byte[data.Length-2];
            Array.Copy(data, 2, command, 0, command.Length);
            return command;
        }
        
        /// <summary>
        /// İstemci Sockete Veri yolla
        /// </summary>
        /// <param name="data">Yollanacak Veri</param>
        private void SendBytes(byte[] data)
        {
            this.ClientSocket.BeginSend(data, 0, data.Length, SocketFlags.None, new AsyncCallback(OnSend), null);
        }
        
         /// <summary>
         /// Gelen Paketi Ayrıştırıp Command Packetleri Ayır
         /// </summary>
         /// <param name="data">Gelen Veri</param>
         /// <param name="ret">Gelen Verinin Boyutu</param>
        private void ExtractBuffer(byte[] data, int ret)
        {
            byte[] temp = new byte[ret];
            Array.Copy(data, temp, ret);
            byte[] ReelCommand = new byte[ret - 2];
            Array.Copy(temp, 2, ReelCommand, 0, ret - 2);
            if (temp[0] == 251)
            {
                XCommunicateEnum opt = (XCommunicateEnum)temp[1];
                switch (opt)
                {
                    case XCommunicateEnum.LoginRequestResponse:
                        if (OnLoginRequestResponse != null)
                            OnLoginRequestResponse(LoginRequestResponse.ParseFrom(GetCommand(data)));
                        Console.WriteLine("Login Request Response Received..");
                        break;
                    case XCommunicateEnum.UserInfoRequestResponse:
                        if (OnUserInfoRequestResponse != null)
                            OnUserInfoRequestResponse(UserInfoRequestResponse.ParseFrom(GetCommand(data)));
                        Console.WriteLine("User Info Request Response Received..");
                        break;
                    case XCommunicateEnum.GamePermRequestResponse:
                        if (OnGamePermRequestResponse != null)
                            OnGamePermRequestResponse(GamePermRequestResponse.ParseFrom(GetCommand(data)));
                        Console.WriteLine("Game Perm Request Response Received..");
                        break;
                    default:
                        Console.WriteLine("A Invalid Packet Received..");
                        break;
                }
            }

        }
       
         /// <summary>
        /// İstemciyi yoket
        /// </summary>
        public void Dispose()
        {
            this.ClientSocket = null;
            this.Name = null;          
        }
        
        /// <summary>
        /// Yeni Bi istemci Kur
        /// </summary>
        /// <param name="nLocalSocket">Dinleyici Sockete Gelen Bağlantı</param>
        public XReactorSockets(IPEndPoint nServerEP)
        {
            m_ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            this.ServerEp = nServerEP;
        }

        #endregion
         
    }
}
